// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package textproto

import (
	"sync"
)

// 管道按照顺序请求/响应顺序管理一个管道。
// 
// 若要使用管道p来管理连接上的多个客户端，
// 每个客户端应运行：
// 
// id:=p.Next（）
// 
// p.StartRequest（id）
// «发送请求»
// p.EndRequest（id）
// 
// p.StartResponse（id）
// «读取响应»
// p.EndResponse（id）
// 
// 管道服务器可以使用相同的调用来确保以正确顺序写入并行计算的
// 响应。
type Pipeline struct {
	mu       sync.Mutex
	id       uint
	request  sequencer
	response sequencer
}

// Next返回请求/响应对的下一个id。
func (p *Pipeline) Next() uint {
	p.mu.Lock()
	id := p.id
	p.id++
	p.mu.Unlock()
	return id
}

// StartRequest将一直阻止，直到发送（或者，如果这是服务器，则接收）
// 具有给定id的请求。
func (p *Pipeline) StartRequest(id uint) {
	p.request.Start(id)
}

// EndRequest通知p具有给定id的请求已发送
// （或者，如果这是服务器，则已接收）。
func (p *Pipeline) EndRequest(id uint) {
	p.request.End(id)
}

// StartResponse会一直阻塞，直到接收（或者，如果这是服务器，则发送）
// 具有给定id的请求。
func (p *Pipeline) StartResponse(id uint) {
	p.response.Start(id)
}

// EndResponse通知p已接收到具有给定id的响应
// （或者，如果这是服务器，则发送）。
func (p *Pipeline) EndResponse(id uint) {
	p.response.End(id)
}

// sequencer计划一系列编号事件，这些事件必须按顺序依次发生。事件编号必须从0开始
// 并递增，不得跳过。只要没有2^32个同时挂起的事件，事件编号就可以安全地围绕
// 进行。
type sequencer struct {
	mu   sync.Mutex
	id   uint
	wait map[uint]chan struct{}
}

// Start等待编号为id的事件开始。
// 也就是说，除了第一个事件外，它一直等待End（id-1）调用
// 为止。
func (s *sequencer) Start(id uint) {
	s.mu.Lock()
	if s.id == id {
		s.mu.Unlock()
		return
	}
	c := make(chan struct{})
	if s.wait == nil {
		s.wait = make(map[uint]chan struct{})
	}
	s.wait[id] = c
	s.mu.Unlock()
	<-c
}

// End通知sequencer编号为id的事件已完成，
// 允许其计划编号为id+1的事件。使用非活动事件编号的id调用End是一个运行时错误
// 。
func (s *sequencer) End(id uint) {
	s.mu.Lock()
	if s.id != id {
		s.mu.Unlock()
		panic("out of sync")
	}
	id++
	s.id = id
	if s.wait == nil {
		s.wait = make(map[uint]chan struct{})
	}
	c, ok := s.wait[id]
	if ok {
		delete(s.wait, id)
	}
	s.mu.Unlock()
	if ok {
		close(c)
	}
}
